import os
import sys
from typing import List

def tokens(s: str) -> List[str]:
    out = []
    cur = []
    for c in s:
        if c in '\t\r\n ':
            if cur:
                out.append(''.join(cur))
                cur = []
        elif c in '()':
            if cur:
                out.append(''.join(cur))
                cur = []
            out.append(c)
        else:
            cur.append(c)
    if cur:
        out.append(''.join(cur))
    return out


def parse_lef(db, path: str):
    if not path:
        return
    if not os.path.exists(path):
        print(f"[warn] lef not found: {path}")
        return
    with open(path, 'r', encoding='utf-8', errors='ignore') as f:
        cur_macro = ''
        for line in f:
            line = line.strip()
            if not line or line.startswith('#'):
                continue
            t = tokens(line)
            if len(t) >= 3 and t[0] == 'DATABASE' and t[1] == 'MICRONS':
                try:
                    db.dbu_per_micron = int(float(t[2]))
                except Exception:
                    pass
            elif len(t) >= 2 and t[0] == 'MACRO':
                cur_macro = t[1]
            elif cur_macro and len(t) >= 2 and t[0] == 'CLASS':
                # 记录宏的 CLASS：常见为 CORE/BLOCK/PAD/SPACER/ENDCAP 等
                db.macros_class[cur_macro] = t[1]
            elif cur_macro and len(t) >= 5 and t[0] == 'SIZE':
                # SIZE <w> BY <h>
                try:
                    w = float(t[1])
                    h = float(t[3])
                    db.macros_um[cur_macro] = (w, h)
                except Exception:
                    pass
            elif cur_macro and len(t) >= 1 and t[0] == 'END':
                cur_macro = ''


def parse_def(db, path: str):
    if not path or not os.path.exists(path):
        print(f"[error] def not found: {path}")
        sys.exit(1)
    with open(path, 'r', encoding='utf-8', errors='ignore') as f:
        sec = 'NONE'
        comp_active = False
        cur_inst = None
        cur_net = None
        cur_pin = None
        # ensure containers
        db.nets = getattr(db, 'nets', {})
        db.pins = getattr(db, 'pins', {})
        for raw in f:
            line = raw.strip()
            if not line or line.startswith('#'):
                continue
            t = tokens(line)
            if len(t) >= 5 and t[0] == 'UNITS' and t[1] == 'DISTANCE' and t[2] == 'MICRONS':
                try:
                    db.dbu_per_micron = int(float(t[4]))
                except Exception:
                    pass
            elif len(t) >= 2 and t[0] == 'DIEAREA':
                try:
                    nums = []
                    for s in t:
                        ss = s.replace('-', '')
                        if ss.isdigit():
                            nums.append(int(float(s)))
                    if len(nums) >= 4:
                        db.die_lx, db.die_ly, db.die_ux, db.die_uy = nums[0], nums[1], nums[2], nums[3]
                except Exception:
                    pass
            elif len(t) >= 2 and t[0] == 'COMPONENTS':
                sec = 'COMPONENTS'
                comp_active = False
                cur_inst = None
                cur_net = None
            elif len(t) >= 1 and t[0] == 'END':
                # END <SECTION>
                if sec == 'NETS' and cur_net:
                    cur_net = None
                if sec == 'PINS' and cur_pin:
                    cur_pin = None
                sec = 'NONE'
                comp_active = False
                cur_inst = None
            elif sec == 'COMPONENTS':
                if len(t) >= 2 and t[0] == '-' and t[1] != 'END':
                    # - <inst> <macro>
                    name = t[1]
                    macro = t[2] if len(t) >= 3 else ''
                    cur_inst = {'name': name, 'macro': macro, 'x': 0, 'y': 0, 'orient': 'N', 'placed': False, 'fixed': False}
                    db.insts[name] = cur_inst
                    comp_active = True
                    # 处理同一行内的 + PLACED / + FIXED / UNPLACED
                    try:
                        has_state = any(s in ('PLACED', 'FIXED', 'UNPLACED') for s in t)
                        if has_state:
                            state = 'PLACED' if 'PLACED' in t else ('FIXED' if 'FIXED' in t else 'UNPLACED')
                            if state == 'UNPLACED':
                                cur_inst['placed'] = False
                                cur_inst['fixed'] = False
                            else:
                                raw_tokens = [x for x in t if x not in ('+', '(', ')')]
                                num_idx = [i for i, s in enumerate(raw_tokens) if s.replace('-', '').isdigit()]
                                if len(num_idx) >= 2:
                                    xi = num_idx[0]
                                    yi = num_idx[1]
                                    cur_inst['x'] = int(raw_tokens[xi])
                                    cur_inst['y'] = int(raw_tokens[yi])
                                    # 查找朝向标记
                                    orient = None
                                    for k in range(yi + 1, len(raw_tokens)):
                                        tk = raw_tokens[k]
                                        if tk in ('N', 'S', 'E', 'W', 'FN', 'FS', 'FE', 'FW'):
                                            orient = tk
                                            break
                                    if orient:
                                        cur_inst['orient'] = orient
                                cur_inst['placed'] = (state == 'PLACED')
                                cur_inst['fixed'] = (state == 'FIXED')
                    except Exception:
                        pass
                elif comp_active:
                    # + PLACED/FIXED ... OR UNPLACED
                    if '+' in t:
                        # ignore '+', treat the rest
                        t = [x for x in t if x != '+']
                    if len(t) >= 1 and t[0] == ';':
                        comp_active = False
                        cur_inst = None
                        continue
                    if len(t) >= 1 and t[0] in ('PLACED', 'FIXED', 'UNPLACED'):
                        state = t[0]
                        if state == 'UNPLACED':
                            cur_inst['placed'] = False
                            cur_inst['fixed'] = False
                        else:
                            # 支持坐标是否带括号的两种写法
                            # PLACED x y orient
                            # PLACED ( x y ) orient
                            try:
                                # 查找第一个数字的位置
                                nums = [i for i, s in enumerate(t) if s.isdigit() or (s.replace('-', '').isdigit())]
                                if len(nums) >= 2:
                                    xi = nums[0]
                                    yi = nums[1]
                                    cur_inst['x'] = int(t[xi])
                                    cur_inst['y'] = int(t[yi])
                                    # 朝向通常在坐标后一个位置（去除可能的 ')')
                                    oi = yi + 1
                                    while oi < len(t) and t[oi] in (')', '('):
                                        oi += 1
                                    if oi < len(t):
                                        cur_inst['orient'] = t[oi]
                                else:
                                    # 退化：未找到数字，忽略此行
                                    pass
                            except Exception:
                                pass
                            cur_inst['placed'] = (state == 'PLACED')
                            cur_inst['fixed'] = (state == 'FIXED')
            elif len(t) >= 2 and t[0] == 'NETS':
                sec = 'NETS'
                cur_net = None
            elif sec == 'NETS':
                if len(t) >= 2 and t[0] == '-' and t[1] != 'END':
                    # - <netName>
                    net_name = t[1]
                    db.nets[net_name] = []
                    cur_net = net_name
                    # 同行可能带若干连接 ( inst pin )
                    try:
                        i = 0
                        while i < len(t):
                            if t[i] == '(' and i + 2 < len(t):
                                a1 = t[i + 1]
                                a2 = t[i + 2]
                                if a1 == 'PIN':
                                    db.nets[net_name].append(f"PIN:{a2}")
                                else:
                                    db.nets[net_name].append(a1)
                                # 跳过到 ')' 之后
                                while i < len(t) and t[i] != ')':
                                    i += 1
                            i += 1
                    except Exception:
                        pass
                elif cur_net:
                    if len(t) >= 1 and t[0] == ';':
                        cur_net = None
                    else:
                        # 继续采集行内的 ( inst pin )
                        try:
                            i = 0
                            while i < len(t):
                                if t[i] == '(' and i + 2 < len(t):
                                    a1 = t[i + 1]
                                    a2 = t[i + 2]
                                    if a1 == 'PIN':
                                        db.nets[cur_net].append(f"PIN:{a2}")
                                    else:
                                        db.nets[cur_net].append(a1)
                                    while i < len(t) and t[i] != ')':
                                        i += 1
                                i += 1
                        except Exception:
                            pass
            elif len(t) >= 2 and t[0] == 'PINS':
                sec = 'PINS'
                cur_pin = None
            elif sec == 'PINS':
                if len(t) >= 2 and t[0] == '-' and t[1] != 'END':
                    # - <pinName>
                    pin_name = t[1]
                    cur_pin = pin_name
                    db.pins[pin_name] = db.pins.get(pin_name, {'x': 0, 'y': 0, 'orient': 'N', 'placed': False})
                    # 行内可能带 + PLACED ( x y ) orient
                    try:
                        if 'PLACED' in t or 'FIXED' in t:
                            # 去掉 '+' 和括号以便提取数字
                            raw_tokens = [x for x in t if x not in ('+', '(', ')')]
                            nums = [i for i, s in enumerate(raw_tokens) if s.replace('-', '').isdigit()]
                            if len(nums) >= 2:
                                xi = nums[0]
                                yi = nums[1]
                                db.pins[pin_name]['x'] = int(raw_tokens[xi])
                                db.pins[pin_name]['y'] = int(raw_tokens[yi])
                                db.pins[pin_name]['placed'] = True
                                # orient 可能在坐标后
                                oi = yi + 1
                                if oi < len(raw_tokens):
                                    db.pins[pin_name]['orient'] = raw_tokens[oi]
                    except Exception:
                        pass
                elif len(t) >= 1 and t[0] == ';':
                    cur_pin = None
                elif cur_pin:
                    # 处理后续行中的 + PLACED / + FIXED 等
                    try:
                        tt = [x for x in t if x != '+']
                        if len(tt) >= 1 and tt[0] in ('PLACED', 'FIXED'):
                            nums = [i for i, s in enumerate(tt) if s.replace('-', '').isdigit()]
                            if len(nums) >= 2:
                                xi = nums[0]
                                yi = nums[1]
                                db.pins[cur_pin]['x'] = int(tt[xi])
                                db.pins[cur_pin]['y'] = int(tt[yi])
                                db.pins[cur_pin]['placed'] = True
                                # orient 可能在坐标后一个位置（去除可能的括号）
                                oi = yi + 1
                                while oi < len(tt) and tt[oi] in (')', '('):
                                    oi += 1
                                if oi < len(tt):
                                    db.pins[cur_pin]['orient'] = tt[oi]
                    except Exception:
                        pass
